package osinfo

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"log/slog"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"gitee.com/rogax/kagent/pkg/types"
	"gitee.com/rogax/kagent/pkg/utils"
)

// type OSINFOREQUEST struct {
// 	IP string `json:"ip"`
// }

type OSINFO struct {
	// OSType indicates which type of this machine, Linux, Windows or Other.

	logger   *slog.Logger `json:"-"`
	sysRoot  *string      `json:"-"`
	procPath string       `json:"-"`

	OSType string `json:"os_type,omitempty"`

	OSDistroVersion string `json:"os_distro_version,omitempty"`
	// OSDistroFamily  string `json:"os_distro_family,omitempty"`
	OSDistroName string `json:"os_distro_name,omitempty"`

	KernelName    string `json:"os_kernel,omitempty"`
	KernelRelease string `json:"os_kernel_release,omitempty"`
	// KernelVersion string `json:"kernel_version,omitempty"`
	KernelArch string `json:"kernel_arch,omitempty"`

	HostName    string `json:"hostname,omitempty"`
	IPAddressV4 string `json:"ip_address_v4,omitempty"`

	CPUModel         string `json:"cpu_model,omitempty"`
	CPUPhysicalCores int    `json:"cpu_physical_cores,omitempty"`
	CPULogicalCores  int    `json:"cpu_logical_cores,omitempty"`
	CPUFrequency     string `json:"cpu_frequency,omitempty"`
	CPUCache         string `json:"cpu_cache,omitempty"`
	CPUFlags         string `json:"cpu_flags,omitempty"`

	MemoryTotal      string  `json:"memory_total,omitempty"`
	MemoryTotalBytes float64 `json:"memory_total_bytes,omitempty"`

	SYSDiskPysicalTotalSizeBytes uint64 `json:"sys_disk_physical_total_size_bytes,omitempty"`
	SYSDiskLogicalTotalSizeBytes uint64 `json:"sys_disk_logical_total_size_bytes,omitempty"`
	// DiskLogicalUsedSizeBytes  int `json:"disk_logical_used,omitempty"`
}

func New(sysRoot *string) *OSINFO {
	return &OSINFO{
		sysRoot:  sysRoot,
		procPath: filepath.Join(*sysRoot, "/proc"),
		OSType:   "Other",
	}
}

func (o *OSINFO) Get() (string, error) {
	o.get()
	r := make(map[string]*OSINFO)
	r["osinfo"] = o
	oss, err := json.Marshal(r)
	if err != nil {
		log.Println("无法将数据转换为JSON: ", err)
		return "", err
	}
	return string(oss), nil

}

func (o *OSINFO) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	rsp := types.Response{
		Code:    200,
		Message: "success",
		Data:    nil,
	}
	data, err := o.Get()
	if err != nil {
		rsp.Code = 500
		rsp.Message = err.Error()
		w.WriteHeader(http.StatusInternalServerError)
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(rsp)
		return
	}
	rsp.Data = data
	json.NewEncoder(w).Encode(rsp)
}

func (o *OSINFO) get() *OSINFO {
	// change root path
	// depreciated: do not need chroot now.
	// oldRootF, err := os.Open("/")
	// if err != nil {
	// 	log.Fatal(err)

	// }
	// defer oldRootF.Close()

	// chroot_flag := false
	// if *o.sysRoot != "" && *o.sysRoot != "/" {
	// 	err := syscall.Chroot(*o.sysRoot)
	// 	if err != nil {
	// 		log.Printf("Chroot to %s failed: %v", *o.sysRoot, err)
	// 		return o
	// 	}
	// 	chroot_flag = true
	// }

	// // o.GetHostname()

	// // exit chroot
	// if chroot_flag {
	// 	err = oldRootF.Chdir()
	// 	if err != nil {
	// 		log.Fatal(err)
	// 	}
	// 	err = syscall.Chroot(".")
	// 	if err != nil {
	// 		log.Fatalf("Chroot to / failed: %v", err)
	// 	}
	// }

	// getOSDistro get os distro info from /etc/os-release
	// getMeminfoFromProcFS get Memory Total Bytes from procfs
	// getKernel get kernel info from Uname command
	o.getOSDistroFromlib().getMemTotalFromProcFS().getKernel().GetCPU().GetMemory().GetSYSDiskTotal().GetIPAddress().GetHostname()

	return o
}

func (o *OSINFO) getOSDistroFromlib() *OSINFO {
	os_release_collector := NewOSReleaseCollectorWithFilenames(o.logger, o.sysRoot)

	os_release_collector.Collect()

	o.OSDistroName = os_release_collector.os.Name
	o.OSDistroVersion = os_release_collector.os.Version

	return o
}
func (o *OSINFO) getOSDistro() *OSINFO {
	output, err := os.ReadFile(filepath.Join(*o.sysRoot, "/etc/os-release"))
	if err != nil {
		log.Println("Read /etc/os-release failed: ", err)
		return o
	}
	// output = bytes.TrimSpace(output)
	lines := strings.Split(string(output), "\n")
	for _, line := range lines {
		fields := strings.Split(line, "=")
		if len(fields) != 2 {
			continue
		}
		key := strings.TrimSpace(fields[0])
		value := strings.TrimSpace(fields[1])
		value = strings.Trim(value, "\"")
		switch key {
		case "NAME":
			o.OSDistroName = value
		case "VERSION":
			o.OSDistroVersion = value
		}
	}
	return o
}

func (o *OSINFO) getKernel() *OSINFO {
	o.GetKernelFromUnix()
	// o.GetKernelFromCommand()
	return o
}
func (o *OSINFO) GetKernelFromUnix() *OSINFO {
	uname, err := Uname()
	if err != nil {
		log.Printf("Get kernel info failed: %s", err)
		return o
	}

	o.KernelName = uname.SysName
	o.OSType = uname.SysName
	o.KernelRelease = uname.Release
	o.KernelArch = uname.Machine

	return o
}

func (o *OSINFO) GetKernelFromCommand() *OSINFO {
	if _, err := exec.LookPath("uname"); err != nil {
		log.Println("无法找到命令uname: ", err)
		return o
	}

	kernel_name, err := exec.Command("uname", "-s").Output()
	if err != nil {
		log.Println("无法获取命令uname -s的标准输出: ", err)
		return o
	}

	kernel_name = bytes.TrimSpace(kernel_name)
	if len(kernel_name) == 0 {
		log.Println("命令uname -s裁剪错误: ", string(kernel_name))
		return o
	}

	if string(kernel_name) == "Linux" {
		o.OSType = "Linux"
	}

	o.KernelName = string(kernel_name)

	kernel_release, _ := exec.Command("uname", "-r").Output()
	// stdout, _ = cmd.StdoutPipe()
	// cmd.Start()
	// k_release, _ := io.ReadAll(stdout)
	kernel_release = bytes.TrimSpace(kernel_release)
	// if len(k_release) == 0 {
	// 	log.Println("命令uname -r输出结果不正确: ", string(output))
	// }
	o.KernelRelease = string(kernel_release)

	kernel_arch, _ := exec.Command("uname", "-m").Output()
	// stdout, _ = cmd.StdoutPipe()
	// cmd.Start()
	// k_arch, _ := io.ReadAll(stdout)
	kernel_arch = bytes.TrimSpace(kernel_arch)
	// if len(kernel_arch) == 0 {
	// 	log.Println("命令uname -m输出结果不正确: ", string(output))
	// }
	o.KernelArch = string(kernel_arch)

	return o
}

func (o *OSINFO) GetHostname() *OSINFO {
	h, err := utils.GetHostname("")
	if err != nil {
		log.Println("无法获取主机名: ", err)
		return o
	}
	o.HostName = h
	return o
}

func (o *OSINFO) GetIPAddress() *OSINFO {
	ip, err := utils.GetIPAddress("")
	if err != nil {
		log.Println("无法获取主机IP: ", err)
		return o
	}
	o.IPAddressV4 = ip
	return o
}

func (o *OSINFO) GetCPU() *OSINFO {
	cpuinfo, err := os.ReadFile("/proc/cpuinfo")
	if err != nil {
		log.Println("Read file /proc/cpuinfo failed: ", err)
		return o
	}
	lines := strings.Split(string(cpuinfo), "\n")
	cpu_logical_cores := 0
	cpu_physical_cores := 0
	for _, line := range lines {
		fields := strings.Split(line, ":")
		if len(fields) != 2 {
			continue
		}
		key := strings.TrimSpace(fields[0])
		value := strings.TrimSpace(fields[1])
		switch key {
		case "model name":
			o.CPUModel = value
			cpu_logical_cores++
		case "physical id":
			v, err := strconv.Atoi(value)
			if err != nil {
				log.Println("无法转换物理CPUID: ", err)
				continue
			}
			if v > cpu_physical_cores {
				cpu_physical_cores = v
			}
		case "cache size":
			o.CPUCache = value
		case "cpu MHz":
			o.CPUFrequency = value
			//case "flags":
			// 	o.CPUFlags = value

		}
	}
	o.CPULogicalCores = cpu_logical_cores
	o.CPUPhysicalCores = cpu_physical_cores + 1
	return o
}

func (o *OSINFO) GetMemory() *OSINFO {
	meminfo, err := os.ReadFile("/proc/meminfo")
	if err != nil {
		log.Println("无法读取文件/proc/meminfo: ", err)
		return o
	}
	lines := strings.Split(string(meminfo), "\n")
	for _, line := range lines {
		fields := strings.Split(line, ":")
		if len(fields) != 2 {
			continue
		}
		key := strings.TrimSpace(fields[0])
		value := strings.TrimSpace(fields[1])
		switch key {
		case "MemTotal":
			o.MemoryTotal = value
		}
	}
	return o
}

type SYSDISK struct {
	Name          string `json:"name"`
	Path          string `json:"path,omitempty"`
	Size          uint64 `json:"size,omitempty"`
	PhysicalBlock int    `json:"physical_block,omitempty"`
	LogicalBlock  int    `json:"logical_block,omitempty"`
}

// GetSYSDiskTotal get the system disk size in all from /sys/block like following:
// lrwxrwxrwx 1 root root 0 Jul 19 14:44 nbd8 -> ../devices/virtual/block/nbd8
// lrwxrwxrwx 1 root root 0 Jul 19 14:44 nbd9 -> ../devices/virtual/block/nbd9
// lrwxrwxrwx 1 root root 0 Jul 19 22:37 sda -> ../devices/pci0000:00/0000:00:02.2/0000:03:00.0/host0/target0:2:0/0:2:0:0/block/sda
// lrwxrwxrwx 1 root root 0 Jul 19 22:37 sdb -> ../devices/pci0000:00/0000:00:02.2/0000:03:00.0/host0/target0:2:1/0:2:1:0/block/sdb
func (o *OSINFO) GetSYSDiskTotal() *OSINFO {
	disks := []SYSDISK{}

	sys_block, err := exec.Command("ls", "-l", "/sys/block/").Output()
	if err != nil {
		log.Println("Command execute to get block device dirname falied: ", err)
		return o
	}

	syspath_match_obj, _ := regexp.Compile(`/devices/pci.*`)

	lines := strings.Split(string(sys_block), "\n")
	for _, line := range lines {
		fields := strings.Fields(line)
		if len(fields) != 11 {
			continue
		}
		syspath := syspath_match_obj.FindString(fields[10])
		if syspath == "" {
			continue
		}
		syspath = "/sys/" + syspath
		name := strings.TrimSpace(fields[8])
		diskpath := "/dev/" + name
		// disk, err := os.Stat(diskpath)
		physical_block_size, err := os.ReadFile(syspath + "/queue/physical_block_size")
		if err != nil {
			log.Println(fmt.Sprintf("Get disk %s physical block size failed: ", diskpath), err)
			continue
		}
		physical_block_size = bytes.TrimSpace(physical_block_size)
		physical_block_size_int, err := strconv.Atoi(string(physical_block_size))
		if err != nil {
			log.Println(fmt.Sprintf("Convert disk %s block size failed: ", diskpath), err)
			continue
		}

		logical_block_size, err := os.ReadFile(syspath + "/queue/logical_block_size")
		if err != nil {
			log.Println(fmt.Sprintf("Get disk %s logical block size failed: ", diskpath), err)
			continue
		}
		logical_block_size = bytes.TrimSpace(logical_block_size)
		logical_block_size_int, err := strconv.Atoi(string(logical_block_size))
		if err != nil {
			log.Println(fmt.Sprintf("Convert disk %s logical block size failed: ", diskpath), err)
			continue
		}

		block_amounts, err := os.ReadFile(syspath + "/size")
		if err != nil {
			log.Println(fmt.Sprintf("Get disk %s disk amounts failed: ", diskpath), err)
			continue
		}
		block_amounts = bytes.TrimSpace(block_amounts)
		block_amounts_int, err := strconv.Atoi(string(block_amounts))
		if err != nil {
			log.Println(fmt.Sprintf("Convert disk %s block amounts failed: ", diskpath), err)
			continue
		}

		disks = append(disks, SYSDISK{
			Name:          name,
			Path:          diskpath,
			Size:          uint64(block_amounts_int),
			PhysicalBlock: physical_block_size_int,
			LogicalBlock:  logical_block_size_int,
		})
	}

	tmpSYSDiskPysicalTotalSizeBytes := uint64(0)
	tmpSYSDiskLogicalTotalSizeBytes := uint64(0)
	for _, disk := range disks {
		tmpSYSDiskPysicalTotalSizeBytes += disk.Size * uint64(disk.PhysicalBlock)
		tmpSYSDiskLogicalTotalSizeBytes += disk.Size * uint64(disk.LogicalBlock)
	}
	if o.SYSDiskLogicalTotalSizeBytes != tmpSYSDiskLogicalTotalSizeBytes {
		o.SYSDiskLogicalTotalSizeBytes = tmpSYSDiskLogicalTotalSizeBytes
	}
	if o.SYSDiskPysicalTotalSizeBytes != tmpSYSDiskPysicalTotalSizeBytes {
		o.SYSDiskPysicalTotalSizeBytes = tmpSYSDiskPysicalTotalSizeBytes
	}

	return o
}
